package com.sec.android.app.myfiles.widget.listview.pinchview;

import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Handler;
import android.support.v4.view.animation.PathInterpolatorCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.GridView;
import android.widget.ListAdapter;

import com.sec.android.app.myfiles.activity.AbsMyFilesActivity;
import com.sec.android.app.myfiles.decorator.gridview.GridViewDecorator;
import com.sec.android.app.myfiles.feature.MultiWindowMgr;
import com.sec.android.app.myfiles.log.Log;
import com.sec.android.app.myfiles.log.SamsungAnalyticsLog;
import com.sec.android.app.myfiles.util.UiUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;


public class PinchGridView extends GridView implements OnScaleGestureListener, AnimatorListener {
    private enum DIRECTION {
        DIRECTION_NONE, DIRECTION_PINCH_IN, DIRECTION_PINCH_OUT,
    }

    private DIRECTION mPinchDirection = DIRECTION.DIRECTION_NONE;

    private SamsungAnalyticsLog.Event mSAEvent;

    private int mPrevCulNum = -1;
    private int mPrevPinchDepth = -1;

    private ListAdapter mAdapter = null;

    private boolean mIsLayouted = false;

    private boolean mNeedToCreateAnimation = false;

    private boolean mIsOnLayoutAnimation = false;

    private PinchAnimationFactory mAnimationList = null;

    private float mCurrentAnimationProgress = 0.0f;

    private boolean mEnabledPinchOperation = true;

    private long mPinchStartTime = 0;

    private static final int LAYOUT_ANIMATION_DURATION = 666 * 2;
    private static final float MOVE_ANIMATION_THRESHOLD = 0.09f;

    private static final long PINCH_FLICK_THRESHOLD_TIME = 1;
    private static final float MIN_PINCH_DISTANCE = 1.0f;
    private static final float MAX_PINCH_DISTANCE = 50.0f;

    private ScaleGestureDetector mScaleGestureDetector = null;


    private float mPrevSpan = 1.0f;
    private float mCurrentSpan = 1.0f;
    /*
     * Scroll Property
     */
    private boolean mIsNeedToRecoverColumnNum = false;

    private int mPrevFirstIndex = -1;
    private int mPrevFirstViewTop = 0;

    private ArrayList<AnimationViewInfo> mAnimationViewInfoList;
    private ArrayList<GridViewItemInfo> mCurViewInfo;
    private int mListPosition;
    private View mEmptyView;
    private MultiWindowMgr mMultiWindowMgr;

    public interface OnLayoutAnimationListener {
        void onAnimationStart(PinchGridView gridViewEx);

        void onAnimationEnd(PinchGridView gridViewEx);
    }

    private OnLayoutAnimationListener mLayoutAnimationListener = null;

    public static final int MAX_PINCH_DEPTH = 2;

    public static final int MIN_PINCH_DEPTH = 0;

    private ArrayList<GridViewItemInfo> mPrevViewInfoList = null;


    public PinchGridView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    public PinchGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public PinchGridView(Context context) {
        super(context);
        init(context);
    }

    public void setOnLayoutAnimationListener(OnLayoutAnimationListener listener) {
        mLayoutAnimationListener = listener;
    }

    public void setEnablePinchOperation(boolean enable) {
        mEnabledPinchOperation = enable;
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        mAdapter = adapter;
        super.setAdapter(adapter);
    }

    private void init(Context context) {
        mPrevViewInfoList = new ArrayList<>();
        mAnimationList = new PinchAnimationFactory();
        mScaleGestureDetector = new ScaleGestureDetector(getContext(), this);
        mScaleGestureDetector.setQuickScaleEnabled(false);
        mMultiWindowMgr = MultiWindowMgr.getInstance((AbsMyFilesActivity) context, ((AbsMyFilesActivity) context).getProcessId());
    }

    @Override
    public void setNumColumns(int numColumns) {
        setNumColumnsWithoutAnimation(numColumns);
    }

    private void setNumColumnsWithAnimation(int numColumns) {
        if (mIsOnLayoutAnimation) {
            Log.e(this, "setNumColumnsWithAnimation : On layout animation");
            return;
        }

        if (numColumns <= 0) {
            Log.e(this, "setNumColumnsWithAnimation : invalid columns number is requested");
            return;
        }

        mIsLayouted = false;
        saveListProperty();

        if (getChildCount() > 0) {
            scrapCurrentViews();
            mNeedToCreateAnimation = true;
        }

        if (mPrevFirstIndex > 0) {
            setSelection(mPrevFirstIndex);
        }
        Log.d(this, "setNumColumnsWithAnimation " + numColumns);
        super.setNumColumns(numColumns);
    }

    private void setNumColumnsWithoutAnimation(int numColumns) {
        if (mIsOnLayoutAnimation) {
            Log.e(this, "setNumColumnsWithoutAnimation : On layout animation");
            return;
        }

        if (numColumns <= 0) {
            Log.e(this, "setNumColumnsWithoutAnimation : numColumns is less than 0");
            return;
        }

        mIsLayouted = false;
        mPrevCulNum = getNumColumns();
        mNeedToCreateAnimation = false;


        Log.d(this, "setNumColumnsWithoutAnimation " + numColumns);
        super.setNumColumns(numColumns);
    }

    @Override
    public View getEmptyView() {
        View ret = mEmptyView;
        if (mEmptyView == null) {
            ret = super.getEmptyView();
        }
        return ret;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldW, int oldH) {
        if (w > 0) {
            if (mEmptyView != null) {
                super.setEmptyView(mEmptyView);
                mEmptyView = null;
            }

            int colCnt = getColCnt(mPinchDepth);
            super.setNumColumns(colCnt);
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    requestLayout();
                }
            });
        }
        super.onSizeChanged(w, h, oldW, oldH);
    }


    @Override
    public void setEmptyView(View emptyView) {

        mEmptyView = emptyView;
        if (mEmptyView != null && mEmptyView.getVisibility() == View.GONE) {
            mEmptyView.setVisibility(View.VISIBLE);
        }
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (!mEnabledPinchOperation) {
            super.onLayout(changed, l, t, r, b);
            mIsLayouted = true;
            return;
        }

        if (mNeedToCreateAnimation) super.setAdapter(mAdapter);

        super.onLayout(changed, l, t, r, b);

        if (mIsNeedToRecoverScrollPos) {
            View childView = getChildAt(0);
            if (childView != null) {
                childView.setTop(childView.getTop() + mPrevFirstViewTop);
                childView.setBottom(childView.getBottom() + mPrevFirstViewTop);
            }

            mPrevFirstViewTop = 0;
            mIsNeedToRecoverScrollPos = false;
            super.layoutChildren();
        }

        mIsLayouted = true;
        createPinchEffect();
    }

    private void createPinchEffect() {
        if (mNeedToCreateAnimation) {
            if (mPrevFirstIndex > 0) {
                setSelection(mPrevFirstIndex);
                super.layoutChildren();
            }

            makeNextMoveLayoutAnimationList();
            setLayoutAnimationProgress(0.0f);
            mNeedToCreateAnimation = false;
            GridViewDecorator.setIsPinchWorking();
        }
    }

    private int mPreBottom = -1;

    private void setBottomForAnimation() {
        if (mPreBottom == -1 && !mAnimationList.isEmpty() && mIsLayouted) {
            AnimationViewInfo aniInfo = mAnimationViewInfoList.get(0);
            mPreBottom = getBottom();
            int toHeight = (int) aniInfo.getFromHeight();
            int rowCnt = mPreBottom / (int) aniInfo.getToHeight();
            if (mPreBottom % (int) aniInfo.getToHeight() != 0) {
                rowCnt++;
            }

            int tempBottom = toHeight * rowCnt;
            if (mPreBottom < tempBottom) {
                setSuperBottom(tempBottom);
            }
        }
    }

    @Override
    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
        if (mAnimationList == null) {
            super.onInitializeAccessibilityEvent(event);
        }
    }

    private void restoreBottomForAnimation() {
        if (mPreBottom != -1) {
            setSuperBottom(mPreBottom);
            mPreBottom = -1;
        }
    }

    private void reLayoutChildrenForAnimation() {
        if (!mAnimationList.isEmpty() && mIsLayouted) {
            detachAllViewsFromParent();

            for (AnimationViewInfo aniInfo : mAnimationViewInfoList) {
                View view = aniInfo.getView();
                if (mCurrentAnimationProgress == 0 && !mIsOnLayoutAnimation) {
                    ViewGroup.LayoutParams param = view.getLayoutParams();
                    param.height = (int) aniInfo.getFromHeight();
                    param.width = (int) aniInfo.getFromWidth();
                    view.setX(aniInfo.getFromX());
                    view.setY(aniInfo.getFromY());
                }
                attachViewToParent(view, -1, view.getLayoutParams());
            }
        }
    }


    @Override
    protected void layoutChildren() {
        setBottomForAnimation();
        super.layoutChildren();
        restoreBottomForAnimation();

        reLayoutChildrenForAnimation();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (!mEnabledPinchOperation || mIsOnLayoutAnimation) {
            return super.dispatchTouchEvent(ev);
        }
        if (!isSpenSelect(ev)) {
            mScaleGestureDetector.onTouchEvent(ev);
        }

        boolean bRet = isPinchEffectWorking();
        if (!bRet) {
            bRet = super.dispatchTouchEvent(ev);
        }
        return bRet;
    }

    private boolean isPinchEffectWorking() {
        return mIsOnLayoutAnimation || mNeedToCreateAnimation || !mAnimationList.isEmpty();
    }

    private boolean isSpenSelect(MotionEvent ev) {
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean bRet = isPinchEffectWorking();
        if (!bRet) {
            if (!mIsLayouted) {
                bRet = true;
            }
        }
        if (!bRet) {
            bRet = super.onTouchEvent(ev);
        }

        return bRet;
    }

    private void scrapCurrentViews() {
        mPrevViewInfoList.clear();
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            mPrevViewInfoList.add(new GridViewItemInfo(childView, mPrevFirstIndex + i, mPrevCulNum));
        }
    }


    private void setLayoutAnimationStartPoint(boolean reverse) {
        if (mAnimationList.isEmpty()) {
            return;
        }

        for (Animator animator : mAnimationList.getCurrentAnimationList()) {
            PinchAnimator gridExAni = (PinchAnimator) animator;
            if (reverse)
                gridExAni.reversePoint();
            else
                gridExAni.setStartPoint();
        }
    }

    private void playLayoutAnimation(boolean reverse) {
        if (!mIsLayouted) {
            return;
        }

        if (mIsOnLayoutAnimation) {
            return;
        }

        if (mAnimationList.isEmpty()) {
            return;
        }
        mIsOnLayoutAnimation = true;

        setLayoutAnimationStartPoint(reverse);

        AnimatorSet aniSet = new AnimatorSet();
        aniSet.playTogether(mAnimationList.getCurrentAnimationList());
        aniSet.setDuration(LAYOUT_ANIMATION_DURATION / 2);
        aniSet.setInterpolator(PathInterpolatorCompat.create(0.33f, 0f, 0.2f, 1f));
        aniSet.addListener(this);
        aniSet.start();

        mIsLayouted = false;
        mCurrentAnimationProgress = 0.0f;
    }

    private void makeNextMoveLayoutAnimationList() {
        if (mIsOnLayoutAnimation) {
            return;
        }

        mAnimationList.clear();

        if (mCurViewInfo != null) {
            mCurViewInfo.clear();
        }
        mCurViewInfo = new ArrayList<>();


        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            mCurViewInfo.add(new GridViewItemInfo(childView, getFirstVisiblePosition() + i, getNumColumns()));
        }
        mAnimationViewInfoList = createAnimationView(mPrevViewInfoList, mCurViewInfo);

        if (mAnimationViewInfoList != null) {
            for (AnimationViewInfo animationViewInfo : mAnimationViewInfoList) {
                Log.d(this, "Ani " + animationViewInfo);
            }

            for (AnimationViewInfo animationViewInfo : mAnimationViewInfoList) {
                mAnimationList.createTransXAnimation(animationViewInfo.getView(), LAYOUT_ANIMATION_DURATION, animationViewInfo.getFromX(), animationViewInfo.getToX());
                mAnimationList.createTransYAnimation(animationViewInfo.getView(), LAYOUT_ANIMATION_DURATION, animationViewInfo.getFromY(), animationViewInfo.getToY());
                mAnimationList.createWidthAnimation(animationViewInfo.getView(), LAYOUT_ANIMATION_DURATION, animationViewInfo.getFromWidth(), animationViewInfo.getToWidth());
                mAnimationList.createHeightAnimation(animationViewInfo.getView(), LAYOUT_ANIMATION_DURATION, animationViewInfo.getFromHeight(), animationViewInfo.getToHeight());

                PinchAnimator.updateView(animationViewInfo.getView(),
                        animationViewInfo.getFromX(),
                        animationViewInfo.getFromY(),
                        (int) animationViewInfo.getFromWidth(),
                        (int) animationViewInfo.getFromHeight()
                );

                animationViewInfo.getView().setVisibility(VISIBLE);
            }
        }
    }

    private ArrayList<AnimationViewInfo> createAnimationView(ArrayList<GridViewItemInfo> preViewList, ArrayList<GridViewItemInfo> curViewList) {
        ArrayList<AnimationViewInfo> ret = new ArrayList<>();

        int startPos = curViewList.get(0).mPosition;
        int sLastPos = curViewList.get(curViewList.size() - 1).mPosition;
        if (preViewList.get(0).mPosition < startPos) {
            startPos = preViewList.get(0).mPosition;
        }

        if (preViewList.get(preViewList.size() - 1).mPosition > sLastPos) {
            sLastPos = preViewList.get(preViewList.size() - 1).mPosition;
        }

        AnimationViewInfo.clearBase();
        for (int i = startPos; i <= sLastPos; i++) {
            GridViewItemInfo toView = getItemByPosition(i, curViewList);
            GridViewItemInfo fromView = getItemByPosition(i, preViewList);

            AnimationViewInfo animationViewInfo = AnimationViewInfo.createNew(fromView, toView);
            ret.add(animationViewInfo);
        }

        return ret;
    }

    private GridViewItemInfo getItemByPosition(int position, ArrayList<GridViewItemInfo> viewList) {
        GridViewItemInfo ret = null;
        int nColSize = 0;
        for (GridViewItemInfo viewInfo : viewList) {
            nColSize = viewInfo.mColSize;
            if (viewInfo.mPosition == position) {
                ret = viewInfo;
                break;
            }
        }

        if (ret == null) {
            ret = new GridViewItemInfo(position, nColSize);
        }

        return ret;
    }

    private void setLayoutAnimationProgress(float progress) {
        if (mAnimationList.isEmpty()) {
            return;
        }

        float duration = Math.abs(mCurrentAnimationProgress - progress);

        for (Animator animator : mAnimationList.getCurrentAnimationList()) {
            PinchAnimator gridExAni = (PinchAnimator) animator;
            gridExAni.setFloatValues(mCurrentAnimationProgress, progress);
            long aNewDuration = (long) (gridExAni.getDuration() * (1.0f - duration));
            gridExAni.setDuration(aNewDuration);
            gridExAni.setCurrentPlayTime(0);
            gridExAni.start();
        }

        mCurrentAnimationProgress = progress;
    }

    boolean mIsNeedToRecoverScrollPos = false;

    @Override
    public int getChildCount() {
        if (mAnimationViewInfoList != null && !mAnimationViewInfoList.isEmpty()) {
            return mAnimationViewInfoList.size();
        }
        return super.getChildCount();
    }

    @Override
    public View getChildAt(int index) {
        View ret = null;
        if (index >= 0) {
            if (mAnimationViewInfoList != null && !mAnimationViewInfoList.isEmpty()) {
                if (index < mAnimationViewInfoList.size() && mAnimationViewInfoList.get(index) != null) {
                    ret = mAnimationViewInfoList.get(index).getView();
                }
            } else {
                ret = super.getChildAt(index);
            }

        }
        return ret;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        AnimationViewInfo.clearBase();
    }

    private void recoverPrevColNum() {
        Log.e(this, "recoverPrevColNum");
        if (mPrevCulNum < 0) {
            return;
        }

        if (mPrevFirstIndex > 0 || mPrevFirstViewTop != 0) {
            setSelection(mPrevFirstIndex);
            mIsNeedToRecoverScrollPos = true;
        }

        if (getNumColumns() < mPrevCulNum) {
            mPinchDepth++;
        } else {
            mPinchDepth--;
        }
        if (mPinchDepth > UiUtils.getMaxPinchDepth()) mPinchDepth = UiUtils.getMaxPinchDepth();
        if (mPinchDepth < 0) mPinchDepth = MIN_PINCH_DEPTH;

        setNumColumnsWithoutAnimation(mPrevCulNum);

        // reset values
        mIsNeedToRecoverColumnNum = false;
        mPrevCulNum = -1;
        mPrevFirstIndex = -1;
        mPrevPinchDepth = -1;
    }

    private void saveListProperty() {
        mPrevCulNum = -1;
        mPrevFirstIndex = -1;
        mPrevFirstViewTop = 0;

        if (getChildCount() == 0) {
            return;
        }

        final View firstView = getChildAt(0);

        if (firstView != null) {
            mPrevFirstIndex = getFirstVisiblePosition();
            mPrevFirstViewTop = firstView.getTop();
        }
        mPrevCulNum = getNumColumns();
    }

    private int mColumnWidthInfo[];
    private int mPinchDepth;


    public void setPinchDepth(int depth) {
        mPinchDepth = depth;
        setColumnWidth(mColumnWidthInfo[depth]);
        this.setNumColumns(getColCnt(depth));
    }

    private int getColCnt(int depth) {
        int ret = AUTO_FIT;
        if (mColumnWidthInfo != null) {
            int width = getWidth() - getPaddingStart() - getPaddingEnd();
            if (width > 0) {
                ret = width / mColumnWidthInfo[depth];
            }
        }
        return ret;
    }

    public int getPinchDepth() {
        return mPinchDepth;
    }

    public void setColumnWidthList(int columnWidth[]) {
        mColumnWidthInfo = columnWidth;
    }

    @Override
    public void setColumnWidth(int columnWidth) {
        Log.d(this, "setColumnWidth" + columnWidth);
        super.setColumnWidth(columnWidth);
    }

    public DIRECTION getPinchDirection(ScaleGestureDetector detector) {
        DIRECTION ret = DIRECTION.DIRECTION_NONE;

        final float prevSpan = mCurrentSpan;
        mCurrentSpan = setCurrentSpan(detector, mCurrentSpan);
        float directionDistance = mCurrentSpan - mPrevSpan;

        if (Math.abs(directionDistance) > MIN_PINCH_DISTANCE) {
            mPrevSpan = prevSpan;
            Log.d(this, "getPinchDirection: mPrevSpan = " + mPrevSpan + " mCurrentSpan = " + mCurrentSpan);
            if (mPrevSpan > mCurrentSpan) {
                ret = DIRECTION.DIRECTION_PINCH_IN;
            } else if (mPrevSpan < mCurrentSpan) {
                ret = DIRECTION.DIRECTION_PINCH_OUT;
            }

            if (ret == DIRECTION.DIRECTION_PINCH_IN) {
                if (mPinchDepth >= UiUtils.getMaxPinchDepth()) {
                    ret = DIRECTION.DIRECTION_NONE;
                }

            } else {
                if (mPinchDepth == 0) {
                    ret = DIRECTION.DIRECTION_NONE;
                }
            }
        }

        return ret;
    }

    private float setCurrentSpan(ScaleGestureDetector detector, float current) {
        float ret = detector.getCurrentSpan();
        if (Math.abs(ret - current) > MAX_PINCH_DISTANCE) {
            int plus_minus = 1;
            if (ret - current < 0) {
                plus_minus = -1;
            }

            ret = MAX_PINCH_DISTANCE * plus_minus + current; //it means ret  - prevSpan =   MAX_PINCH_DISTANCE;
        }
        return ret;
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return super.drawChild(canvas, child, drawingTime);
    }

    // Scale Listener
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (!mEnabledPinchOperation || mIsOnLayoutAnimation) {
            return false;
        }

        if (mMultiWindowMgr != null && mMultiWindowMgr.isMultiWindow()) {
            return false;
        }

        final long pinchDuration = detector.getEventTime() - mPinchStartTime;
        float distance = detector.getCurrentSpan() - mCurrentSpan;
        boolean validDistance = false;

        float span = setCurrentSpan(detector, mCurrentSpan);

        float directionDistance = span - mPrevSpan;
        if (Math.abs(directionDistance) > MIN_PINCH_DISTANCE) {
            validDistance = true;
        }

        if (validDistance) {
            mPinchDirection = getPinchDirection(detector);

            if (mAnimationList.isEmpty()) {
                if (mPinchDirection != DIRECTION.DIRECTION_NONE) {
                    boolean zoomIn = false;
                    mPrevPinchDepth = mPinchDepth;
                    if (mPinchDirection == DIRECTION.DIRECTION_PINCH_IN) {
                        Log.d(this, "mPinchDirection = DIRECTION.DIRECTION_PINCH_IN");
                        mPinchDepth++;
                    } else {
                        Log.d(this, "mPinchDirection = DIRECTION.DIRECTION_PINCH_OUT");
                        mPinchDepth--;
                        zoomIn = true;
                    }
                    if (mPinchDepth < MIN_PINCH_DEPTH) mPinchDepth = MIN_PINCH_DEPTH;
                    if (mPinchDepth >= mColumnWidthInfo.length)
                        mPinchDepth = mColumnWidthInfo.length - 1;
                    if (mPinchDepth > UiUtils.getMaxPinchDepth())
                        mPinchDepth = UiUtils.getMaxPinchDepth();
                    setNumColumnsWithAnimation(getColCnt(mPinchDepth));
                    requestLayout();
                    setSALogEvent(zoomIn);
                } else {
                    Log.d(this, "mPinchDirection = DIRECTION.DIRECTION_NONE");
                }
            } else {
                float nextProgress = getNextProgress(distance);

                if (pinchDuration < PINCH_FLICK_THRESHOLD_TIME) {
                    return true;
                }
                setLayoutAnimationProgress(nextProgress);
            }
        } else {
            Log.d(this, "onScale invalid distance");
        }

        return true;
    }

    private void setSALogEvent(boolean zoomIn) {
        mSAEvent = zoomIn ? SamsungAnalyticsLog.Event.ZOOM_IN : SamsungAnalyticsLog.Event.ZOOM_OUT;
    }

    public SamsungAnalyticsLog.Event getSAEvent() {
        return mSAEvent;
    }

    private static final float MAX_PROGRESS_STEP = 0.2f;
    private static final float MAX_PROGRESS_STEP_NEGATIVE = -0.2f;

    private float getNextProgress(float distance) {
        float distanceProgress = distance / ((float) getWidth() / 2.0f);
        float nextProgress = mCurrentAnimationProgress;

        if (mPrevPinchDepth < mPinchDepth) {
            distanceProgress *= -1;
        }

        if (distanceProgress > MAX_PROGRESS_STEP) {
            distanceProgress = MAX_PROGRESS_STEP;
        } else if (distanceProgress < MAX_PROGRESS_STEP_NEGATIVE) {
            distanceProgress = MAX_PROGRESS_STEP_NEGATIVE;
        }

        nextProgress += distanceProgress;

        nextProgress = Math.max(0.001f, nextProgress);
        nextProgress = Math.min(1.0f, nextProgress);

        return nextProgress;
    }


    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        Log.d(this, "onScaleBegin");
        MotionEvent motionEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
        super.onTouchEvent(motionEvent);
        motionEvent.recycle();

        mListPosition = getFirstVisiblePosition();
        mPinchStartTime = detector.getEventTime();
        mCurrentSpan = detector.getCurrentSpan();
        mPrevSpan = mCurrentSpan;

        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        Log.d(this, "onScaleEnd");
        mPinchStartTime = 0;
        mCurrentSpan = 0.0f;
        mPrevSpan = 0.0f;

        mPinchDirection = DIRECTION.DIRECTION_NONE;

        if (mIsOnLayoutAnimation) {
            Log.d(this, "onScaleEnd : On layout animation");
            return;
        }

        if (mNeedToCreateAnimation) {
            mNeedToCreateAnimation = false;
            return;
        }

        mIsNeedToRecoverColumnNum = false;

        if (mCurrentAnimationProgress < MOVE_ANIMATION_THRESHOLD) {
            mIsNeedToRecoverColumnNum = true;
        }
        playLayoutAnimation(mIsNeedToRecoverColumnNum);

        mIsLayouted = true;
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        Log.d(this, "onAnimationCancel");
        clearAnimationInfo();
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        Log.d(this, "onAnimationEnd");
        clearAnimationInfo();
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
    }

    @Override
    public void onAnimationStart(Animator animation) {
        Log.d(this, "onAnimationStart");
        if (mLayoutAnimationListener != null)
            mLayoutAnimationListener.onAnimationStart(this);
    }


    public void clearAnimationInfo() {
        mIsOnLayoutAnimation = false;
        Log.d(this, "clearAnimationInfo");

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child != null) {
                child.setLayerType(LAYER_TYPE_NONE, null);
            }
        }

        if (mIsNeedToRecoverColumnNum) {
            Log.d(this, "onAnimationEnd : recoverPrevColNum");
            recoverPrevColNum();
        }
        super.setAdapter(mAdapter);
        setSelection(mListPosition);

        if (mLayoutAnimationListener != null)
            mLayoutAnimationListener.onAnimationEnd(this);

        mAnimationList.clear();

        mAnimationViewInfoList.clear();
        GridViewDecorator.unsetIsPinchWorking();
    }


    boolean setSuperBottom(int bottom) {
        boolean ret = false;
        try {
            Field field = getClass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("mBottom");
            field.setAccessible(true);
            field.setInt(this, bottom);
        } catch (Exception e) {
            Log.e(this, e.getMessage());
        }
        return ret;
    }
}